package com.zw.cc.redis;

import com.zw.cc.exception.ShardJedisOperationException;
import com.zw.cc.redis.util.SerializeUtil;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.ScanParams;
import redis.clients.jedis.ScanResult;
import redis.clients.jedis.ShardedJedis;

import java.util.*;
import java.util.Map.Entry;

public class ShardedJedisSentinelTemplate implements ShardedJedisSentinelOperations {

	private ShardedJedisSentinelPool shardedJedisSentinelPool;

	public ShardedJedisSentinelTemplate(ShardedJedisSentinelPool shardedJedisSentinelPool) {
		super();
		this.shardedJedisSentinelPool = shardedJedisSentinelPool;
	}

	@Override
	public <T> T execute(ConnectionCallback<T, ShardedJedis> action) {
		ShardedJedis shardJedis = null;
		boolean broken = false;
		try {
			shardJedis = shardedJedisSentinelPool.getResource();
			return action.doInConnection(shardJedis);
		} catch (Exception e) {
			broken = true;
			throw new ShardJedisOperationException("ShardedJedis Execute Failed!", e);
		} finally {
			if (null != shardJedis) {
				shardedJedisSentinelPool.returnResource(shardJedis, broken);
			}
		}
	}

	@Override
	public boolean exist(final String key) throws ShardJedisOperationException {
		return execute(jedis -> jedis.exists(key));
	}

	@Override
	public long expire(final String key, final int seconds) throws ShardJedisOperationException {
		return execute(jedis -> {
            if (null == key || "".equals(key))
                return 0L;
            try {
                return jedis.expire(key, seconds);
            } catch (Exception e) {
                throw new ShardJedisOperationException(String.format("Setup expired time failed!!! key:[%s] || secondes:[%s]", key, seconds), e);
            }
        });
	}

	@Override
	public long expireAt(final String key, final long unixTime) throws ShardJedisOperationException {
		return execute(jedis -> {
            if (null == key || "".equals(key)) {
                return 0L;
            }
            try {
                return jedis.expireAt(key, unixTime);
            } catch (Exception e) {
                throw new ShardJedisOperationException(String.format("Setup expiredAt time failed!!! key:[%s] || secondes:[%s]", key, unixTime), e);
            }
        });
	}

	@Override
	public boolean set(final String key, final String value) throws ShardJedisOperationException {
		return execute(jedis -> {
            try {
                jedis.set(key, value);
                return true;
            } catch (Exception e) {
                throw new ShardJedisOperationException(String.format("Save data to redis failed!!! key:[%s] || value:[%s]", key, value), e);
            }
        });
	}

	@Override
	public boolean set(final String key, final String value, final int seconds) throws ShardJedisOperationException {
		return execute(jedis -> {
            try {
                jedis.setex(key, seconds, value);
                return true;
            } catch (Exception e) {
                throw new ShardJedisOperationException(String.format("Save data to redis failed!!! key:[%s] || value:[%s]", key, value), e);
            }
        });
	}

	@Override
	public String get(final String key) throws ShardJedisOperationException {
		return execute(jedis -> {
            try {
                if (exist(key)) {
                    return jedis.get(key);
                } else {
                    return null;
                }
            } catch (Exception e) {
                throw new ShardJedisOperationException(String.format("get data from redis failed!!! key:[%s]", key), e);
            }
        });
	}

	@Override
	public String get(final String key, final String defaultValue) throws ShardJedisOperationException {
		return execute(jedis -> {
            try {
                if (exist(key)) {
                    return jedis.get(key);
                } else {
                    return defaultValue;
                }
            } catch (Exception e) {
                throw new ShardJedisOperationException(String.format("get data from redis failed!!! key:[%s] || defaultValue:[%s]", key, defaultValue), e);
            }
        });
	}

	@Override
	public Object getValue(final String key) throws ShardJedisOperationException {
		return execute(jedis -> {
            try {
                Object ret = null;
                // 去redis中取回序列化后的对象
                byte[] obj = jedis.get(key.getBytes());

                // 取回的对象反序列化
                if (obj != null) {
                    ret = SerializeUtil.unSerialize(obj);
                }
                return ret;
            } catch (Exception e) {
                throw new ShardJedisOperationException(String.format("get data from redis failed!!! key:[%s]", key), e);
            }
        });
	}

	@Override
	public boolean del(final String key) throws ShardJedisOperationException {
		return execute(jedis -> {
            try {
                jedis.del(key);
                return true;
            } catch (Exception e) {
                throw new ShardJedisOperationException(String.format("del data from redis failed!!! key:[%s]", key), e);
            }
        });
	}

	@Override
	public long incr(final String key) throws ShardJedisOperationException {
		return execute(jedis -> {
            try {
                return jedis.incr(key);
            } catch (Exception e) {
                throw new ShardJedisOperationException(String.format("incr failed!!! key:[%s]", key), e);
            }
        });
	}

	@Override
	public long decr(String key) throws ShardJedisOperationException {
		return execute(jedis -> {
            try {
                return jedis.decr(key);
            } catch (Exception e) {
                throw new ShardJedisOperationException(String.format("decr failed!!! key:[%s]", key), e);
            }
        });
	}

	@Override
	public String type(String key) throws ShardJedisOperationException {
		return execute(jedis -> {
            try {
                return jedis.type(key);
            } catch (Exception e) {
                throw new ShardJedisOperationException(String.format("type failed!!! key:[%s]", key), e);
            }
        });
	}

	@Override
	public Long ttl(String key) throws ShardJedisOperationException {
		return execute(jedis -> {
            try {
                return jedis.ttl(key);
            } catch (Exception e) {
                throw new ShardJedisOperationException(String.format("ttl failed!!! key:[%s]", key), e);
            }
        });
	}

	@Override
	public boolean setbit(String key, long offset, boolean value) throws ShardJedisOperationException {
		return execute(jedis -> {
            try {
                return jedis.setbit(key, offset, value);
            } catch (Exception e) {
                throw new ShardJedisOperationException(String.format("setbit failed!!! key:[%s] || offset[%s] || value[%s]", key, offset, value), e);
            }
        });
	}

	@Override
	public boolean getbit(String key, long offset) throws ShardJedisOperationException {
		return execute(jedis -> {
            try {
                return jedis.getbit(key, offset);
            } catch (Exception e) {
                throw new ShardJedisOperationException(String.format("setbit failed!!! key:[%s] || offset[%s]", key, offset), e);
            }
        });
	}

	@Override
	public long setRange(String key, long offset, String value) throws ShardJedisOperationException {
		return execute(jedis -> {
            try {
                return jedis.setrange(key, offset, value);
            } catch (Exception e) {
                throw new ShardJedisOperationException(String.format("setRange failed!!! key:[%s] || offset[%s] || value[%s]", key, offset, value), e);
            }
        });
	}

	@Override
	public String getRange(String key, long startOffset, long endOffset) throws ShardJedisOperationException {
		return execute(jedis -> {
            try {
                return jedis.getrange(key, startOffset, endOffset);
            } catch (Exception e) {
                throw new ShardJedisOperationException(String.format("getRange failed!!! key:[%s] || startOffset[%s] || endOffset[%s]", key, startOffset,
                        endOffset), e);
            }
        });
	}

	@Override
	public String substr(String key, int start, int end) throws ShardJedisOperationException {
		return execute(jedis -> {
            try {
                return jedis.substr(key, start, end);
            } catch (Exception e) {
                throw new ShardJedisOperationException(String.format("substr failed!!! key:[%s] || start[%s] || end[%s]", key, start, end), e);
            }
        });
	}

	@Override
	public long rpush(final String key, final String... values) throws ShardJedisOperationException {
		return execute(jedis -> jedis.rpush(key, values));
	}

	@Override
	public long countSet(final String key) throws ShardJedisOperationException {
		return execute(jedis -> {
            if (null == key) {
                return 0L;
            }
            try {
                return jedis.scard(key);
            } catch (Exception e) {
                throw new ShardJedisOperationException(String.format("countSet failed!!! key:[%s]", key), e);
            }
        });
	}

	@Override
	public boolean addSet(final String key, final String... value) throws ShardJedisOperationException {
		return execute(jedis -> {
            if (null == key || value == null) {
                return false;
            }
            try {
                jedis.sadd(key, value);
                return true;
            } catch (Exception e) {
                throw new ShardJedisOperationException("addSet failed!!! key:"+key+"|| value:"+ Arrays.toString(value), e);
            }
        });
	}

	@Override
	public boolean addSet(final String key, final int seconds, final String... value) throws ShardJedisOperationException {
		boolean result = addSet(key, value);
		if (result) {
			long i = expire(key, seconds);
			return i == 1;
		}
		return false;
	}

	@Override
	public boolean containsInSet(final String key, final String value) throws ShardJedisOperationException {
		return execute(jedis -> {
            if (null == key || null == value)
                return false;
            try {
                return jedis.sismember(key, value);
            } catch (Exception e) {
                throw new ShardJedisOperationException("containsInSet failed!!! key:"+key+",value:"+value, e);
            }
        });
	}

	@Override
	public Set<String> getSet(final String key) throws ShardJedisOperationException {
		return execute(jedis -> {
            try {
                return jedis.smembers(key);
            } catch (Exception e) {
                throw new ShardJedisOperationException(String.format("getSet failed!!! key:[%s]", key), e);
            }
        });
	}

	@Override
	public boolean removeSetValue(final String key, final String... value) throws ShardJedisOperationException {
		return execute(jedis -> {
            try {
                jedis.srem(key, value);
                return true;
            } catch (Exception e) {
                throw new ShardJedisOperationException("removeSetValue failed!!! key:"+key+" || value:"+ Arrays.toString(value), e);
            }
        });
	}

	@Override
	public String trimList(final String key, final long start, final long end) throws ShardJedisOperationException {
		return execute(jedis -> {
            if (null == key || "".equals(key)) {
                return "-";
            }
            try {
                return jedis.ltrim(key, start, end);
            } catch (Exception e) {
                throw new ShardJedisOperationException(String.format("trimList failed!!! key:[%s] || start:[%s] || end:[%s]", key, start, end), e);
            }
        });
	}

	@Override
	public boolean removeListValue(final String key, final long count, final String value) throws ShardJedisOperationException {
		return execute(jedis -> {
            try {
                jedis.lrem(key, count, value);
                return true;
            } catch (Exception e) {
                throw new ShardJedisOperationException(String.format("removeListValue failed!!! key:[%s] || count:[%s] || value:[%s]", key, count, value),
                        e);
            }
        });
	}

	@Override
	public int removeListValue(final String key, final long count, final List<String> values) throws ShardJedisOperationException {
		int rt = 0;
		if (null != values && !values.isEmpty()) {
			for (String value : values) {
				if (removeListValue(key, count, value)) {
					rt++;
				}
			}
		}
		return rt;
	}

	@Override
	public int removeListValue(final String key, final List<String> values) throws ShardJedisOperationException {
		return removeListValue(key, 1, values);
	}

	@Override
	public List<String> rangeList(final String key, final long start, final long end) throws ShardJedisOperationException {
		return execute(jedis -> {
            try {
                return jedis.lrange(key, start, end);
            } catch (Exception e) {
                throw new ShardJedisOperationException(String.format("rangeList failed!!! key:[%s] || start:[%s] || end:[%s]", key, start, end), e);
            }
        });
	}

	@Override
	public long countList(String key) throws ShardJedisOperationException {
		return execute(jedis -> {
            if (null == key)
                return 0L;
            try {
                return jedis.llen(key);
            } catch (Exception e) {
                throw new ShardJedisOperationException(String.format("countList failed!!! key:[%s]", key), e);
            }
        });
	}

	@Override
	public boolean addList(String key, String... value) throws ShardJedisOperationException {
		return execute(jedis -> {
            if (null == key || null == value)
                return false;
            try {
                jedis.lpush(key, value);
                return true;
            } catch (Exception e) {
                throw new ShardJedisOperationException("addList failed!!! key:"+key+" || value:"+ Arrays.toString(value), e);
            }
        });
	}

	@Override
	public boolean addList(String key, List<String> list) throws ShardJedisOperationException {
		if (key == null || list == null || list.isEmpty()) {
			return false;
		}
		for (String value : list) {
			addList(key, value);
		}
		return true;
	}

	@Override
	public boolean addList(final String key, final int seconds, final String... value) throws ShardJedisOperationException {
		boolean result = addList(key, value);
		if (result) {
			long i = expire(key, seconds);
			return i == 1;
		}
		return false;
	}

	@Override
	public List<String> getList(final String key) throws ShardJedisOperationException {
		return execute(jedis -> {
            try {
                return jedis.lrange(key, 0, -1);
            } catch (Exception e) {
                throw new ShardJedisOperationException(String.format("getList failed!!! key:[%s]", key), e);
            }
        });
	}

	@Override
	public boolean setHSet(final String key, final String domain, final String value) throws ShardJedisOperationException {
		return execute(jedis -> {
            if (null == value)
                return false;
            try {
                jedis.hset(key, domain, value);
                return true;
            } catch (Exception e) {
                throw new ShardJedisOperationException(String.format("setHSet failed!!! key:[%s] || domain:[%s] || value:[%s]", key, domain, value), e);
            }
        });
	}

	@Override
	public boolean setHmSet(final String key, final Map<String, String> map) throws ShardJedisOperationException {
		return execute(jedis -> {
            if (map == null || map.isEmpty())
                return false;
            try {
                jedis.hmset(key, map);
                return true;
            } catch (Exception e) {
                throw new ShardJedisOperationException("setHSet failed!!! key:"+key+" value:"+ map.toString(), e);
            }
        });
	}

	@Override
	public Map<String, String> hgetAll(final String key) throws ShardJedisOperationException {
		return execute(jedis -> {
            try {
                return jedis.hgetAll(key);
            } catch (Exception e) {
                throw new ShardJedisOperationException("setHSet failed!!! key:"+ key, e);
            }
        });
	}

	@Override
	public String getHSet(final String key, final String domain) throws ShardJedisOperationException {
		return execute(jedis -> {
            try {
                return jedis.hget(key, domain);
            } catch (Exception e) {
                throw new ShardJedisOperationException(String.format("getHSet failed!!! key:[%s] || domain:[%s]", key, domain), e);
            }
        });
	}

	@Override
	public long delHSet(String key, String... domains) throws ShardJedisOperationException {
		return execute(jedis -> {
            try {
                return jedis.hdel(key, domains);
            } catch (Exception e) {
                throw new ShardJedisOperationException("delHSet failed!!! key:"+key+" || domain:"+ Arrays.toString(domains), e);
            }
        });
	}

	@Override
	public boolean existsHSet(final String key, final String domain) throws ShardJedisOperationException {
		return execute(jedis -> {
            try {
                return jedis.hexists(key, domain);
            } catch (Exception e) {
                throw new ShardJedisOperationException(String.format("existsHSet failed!!! key:[%s] || domain:[%s]", key, domain), e);
            }
        });
	}

	@Override
	public List<Entry<String, String>> scanHSet(final String domain, final String match) throws ShardJedisOperationException {
		return execute(p -> {
            try {
                int cursor = 0;
                //
                ScanParams scanParams = new ScanParams();
                scanParams.match(match);
                //
                Jedis jedis = p.getShard(domain);
                //
                ScanResult<Entry<String, String>> scanResult;
                List<Entry<String, String>> list = new ArrayList<>();
                //
                do {
                    scanResult = jedis.hscan(domain, String.valueOf(cursor), scanParams);
                    list.addAll(scanResult.getResult());
                    cursor = Integer.parseInt(scanResult.getStringCursor());
                } while (cursor > 0);
                return list;
            } catch (Exception e) {
                throw new ShardJedisOperationException(String.format("scanHSet failed!!! domain:[%s] || match:[%s]", domain, match), e);
            }
        });
	}

	@Override
	public List<String> hvals(final String domain) throws ShardJedisOperationException {
		return execute(jedis -> {
            try {
                return jedis.hvals(domain);
            } catch (Exception e) {
                throw new ShardJedisOperationException(String.format("hvals failed!!! domain:[%s]", domain), e);
            }
        });
	}

	@Override
	public Set<String> hkeys(final String domain) throws ShardJedisOperationException {
		return execute(jedis -> {
            try {
                return jedis.hkeys(domain);
            } catch (Exception e) {
                throw new ShardJedisOperationException(String.format("hkeys failed!!! domain:[%s]", domain), e);
            }
        });
	}

	@Override
	public long lenHset(final String domain) throws ShardJedisOperationException {
		return execute(jedis -> {
            try {
                return jedis.hlen(domain);
            } catch (Exception e) {
                throw new ShardJedisOperationException(String.format("lenHset failed!!! domain:[%s]", domain), e);
            }
        });
	}

	@Override
	public boolean setSortedSet(final String key, final long score, final String value) {
		return execute(jedis -> {
            try {
                jedis.zadd(key, score, value);
                return true;
            } catch (Exception e) {
                throw new ShardJedisOperationException(String.format("setSortedSet failed!!! key:[%s] || score:[%s] || value:[%s]", key, score, value), e);
            }
        });
	}

	@Override
	public Set<String> getSortedSet(String key, long startScore, long endScore, boolean orderByDesc) throws ShardJedisOperationException {
		return execute(jedis -> {
            try {
                if (orderByDesc) {
                    return jedis.zrevrangeByScore(key, endScore, startScore);
                } else {
                    return jedis.zrangeByScore(key, startScore, endScore);
                }
            } catch (Exception e) {
                throw new ShardJedisOperationException(String.format(
                        "getSoredSet failed!!! key:[%s] || startScore:[%s] || endScore:[%s] || orderByDesc[%s]", key, startScore, endScore, orderByDesc), e);
            }
        });
	}

	@Override
	public long countSortedSet(final String key, final long startScore, final long endScore) throws ShardJedisOperationException {
		return execute(shardedJedis -> {
            try {
                Long count = shardedJedis.zcount(key, startScore, endScore);
                return count == null ? 0L : count;
            } catch (Exception e) {
                throw new ShardJedisOperationException(String.format("countSoredSet failed!!! key:[%s] || startScore:[%s] || endScore:[%s]", key,
                        startScore, endScore), e);
            }
        });
	}

	@Override
	public boolean delSortedSet(final String key, final String value) throws ShardJedisOperationException {
		return execute(jedis -> {
            try {
                long count = jedis.zrem(key, value);
                return count > 0;
            } catch (Exception e) {
                throw new ShardJedisOperationException(String.format("delSortedSet failed!!! key:[%s] || value:[%s]", key, value), e);
            }
        });
	}

	@Override
	public Long zremrangeByScore(final String key, final double start, final double end) throws ShardJedisOperationException {
		return execute(jedis -> {
            try {
                return jedis.zremrangeByScore(key, start, end);
            } catch (Exception e) {
                throw new ShardJedisOperationException("delSortedSet failed!!! key:"+key+" || start:"+start+"end:"+ end, e);
            }
        });
	}

	@Override
	public Set<String> getSortedSetByRange(final String key, final int startRange, final int endRange, final boolean orderByDesc)
			throws ShardJedisOperationException {
		return execute(jedis -> {
            try {
                if (orderByDesc) {
                    return jedis.zrevrange(key, startRange, endRange);
                } else {
                    return jedis.zrange(key, startRange, endRange);
                }
            } catch (Exception e) {
                throw new ShardJedisOperationException(String.format(
                        "getSortedSetByRange failed!!! key:[%s] || startRange:[%s] || endRange:[%s] || orderByDesc[%s]", key, startRange, endRange,
                        orderByDesc), e);
            }
        });
	}

	@Override
	public Double getScore(String key, String member) throws ShardJedisOperationException {
		return execute(jedis -> {
            try {
                return jedis.zscore(key, member);
            } catch (Exception e) {
                throw new ShardJedisOperationException("getScore failed!!! key:"+key+"|| member:"+member, e);
            }
        });
	}

	@Override
	public Long zcard(String key) throws ShardJedisOperationException {
		return execute(jedis -> {
            try {
                return jedis.zcard(key);
            } catch (Exception e) {
                throw new ShardJedisOperationException("zcard failed!!! key:"+ key, e);
            }
        });
	}

}
